Skip to content

feat(cli): replace the cartesi-machine spawn with @cartesi/machine - #509

Draft
tuler wants to merge 1 commit into
prerelease/v2-alphafrom
claude/replace-xgenext2fs-cartesi-machine-rhhnsk
Draft

tuler wants to merge 1 commit into
prerelease/v2-alphafrom
claude/replace-xgenext2fs-cartesi-machine-rhhnsk

Conversation

@tuler

@tuler tuler commented Aug 5, 2026 •

Copy link
Copy Markdown
Member

Contributes to #72.

That issue asks for the external programs the CLI drives to stop being spawned binaries, and notes that "instead of spawning binaries another possibility is to build NodeJS bindings to native code components". This PR takes that route for the emulator.

Program Before After
cartesi-machine execa, falling back to docker run in the SDK image @cartesi/machine
cartesi-machine-stored-hash execa, falling back to docker run in the SDK image @cartesi/machine

xgenext2fs gets the same treatment in #517, stacked on this branch. mksquashfs, cartesi-rollups-cli and cartesi-rollups-node are untouched by either, so the remaining work on #72 is the Node Unit's binaries.

Note

The branch name still says xgenext2fs-cartesi-machine — it carried both changes before the split, and GitHub does not allow repointing a PR's head branch. Only the machine commit is here now.

What changed

src/machine.ts — the substantial part. The CLI used to hand cartesi-machine a command line and let it assemble the machine configuration. It now does that translation itself, transcribed from cartesi-machine.lua v0.21.0:

  • default dtb.bootargs from the emulator, with machine.boot_args appended
  • dtb.init: the splash, then dev=$(flashdrive <label>) + mount + chown per non-root drive, then dev=$(nvram <label>) + chmod 0664 + chown per nvram, then export K="V", WORKDIR, USER — in the order the old argv produced
  • flash drives with root first, so it lands on /dev/pmem0 regardless of the order drives appear in cartesi.toml
  • nvram ranges in declaration order, since that decides the /dev/uio* device each one gets, sized from size or derived from the backing image
  • virtio console, console=hvc1 and iunrep=1 for cartesi shell

buildMachineConfig is a pure function so this is unit-testable without running a machine; src/exec/cartesi-machine.ts holds the run loop (automatic yields acknowledged, console I/O resumed, guest exit code read from htif_tohost_data).

src/images.ts (new) — with no SDK image to take linux.bin from, the default ram_image is fetched from the pinned cartesi/machine-linux-image v0.21.0 release on first use, verified against its SHA-256 and cached under $XDG_CACHE_HOME/cartesi/images. A CARTESI_IMAGES_PATH directory holding the image is used when set, and machine.ram_image still wins over both.

Console capture — bootMachine grew a captureOutput option. There is no child process to read stdout from any more, and the emulator exposes no API to read a captured console back, so it writes to a temporary file that the run reads once it is over. This is what keeps tests/integration/machine/nvram.test.ts able to assert on what the guest printed.

Breaking

  • Machine hashes change. @cartesi/machine links against emulator 0.21, the SDK image pins 0.20. Applications need redeploying.
  • A snapshot is read by the linked emulator. cartesi hash and cartesi status no longer run cartesi-machine-stored-hash in the project's SDK image, so a snapshot stored by an incompatible emulator reads as no hash and has to be rebuilt. getMachineHash lost its sdk option accordingly, which also drops the now-unused sdk plumbing through run's interactive shell.
  • Boot args are no longer double quoted. The old code passed --append-bootargs="<arg>" with no shell in between, so the quotes ended up in the kernel command line. Building the configuration directly, reproducing that would have meant reproducing a bug.
  • The standalone binaries are gone. A Bun single-file executable has no node_modules, and the addon resolves its platform .node at runtime — a host-native --compile fails too, so cross-compiling four targets was never going to work. The compile step in build.ts and the upload step in release.yaml are removed here. The homebrew formula in cartesi/homebrew-tap needs to install from npm rather than the release tarball. Happy to revert this bit and solve distribution differently if you'd rather keep the binaries.

Rebased onto the nvram and version-check work

The base moved 61 commits while this sat, and a few of them built directly on the code this replaces, so the update was a port rather than a conflict resolution:

  • nvrams (fdfd3c1, 81fedfc, a3cfe1f, 81e6157, bb31cfe, 5fd7421) went in as --nvram= flags. They are now nvram memory ranges in the machine configuration, with the init lines the emulator's own CLI generates for them. Resolving the conflict without porting this would have silently dropped nvram support.
  • the version check (481cc2f, fdd6d93, f48d0a3, c9878d6, 5493e17) probed cartesi-machine --version-json through Docker. requiredVersion, UnsupportedVersionError and assertSupported are kept as they were — they still guard that the linked emulator is in range — while version() is now a local lookup, and assertVersion() takes no options. The unit tests for them are kept, and doctor reports the compiled-in version instead of probing an install.
  • @cartesi/machine@alpha moved from 1.0.0-alpha.0 to 1.0.0-alpha.2, which returns hashes as Uint8Array rather than Buffer; those are converted with viem's bytesToHex.

Testing

  • 234 unit tests pass. tests/unit/machine.test.ts covers the configuration translation (mount points, drive ordering, nvram sizing and ordering, nvram init lines, environment precedence, workdir/user, interactive mode, parseMemorySize); the base branch's nvram cases are carried over as assertions on the configuration rather than on argv.
  • Verified against the real emulator that it accepts the generated nvram ranges, that the init script matches the Lua reference, and that captureOutput returns the guest console.
  • cartesi-machine-stored-hash.test.ts keeps the base branch's isHash assertion, on the new signature. Its "with the project sdk image" case is gone, since hashing no longer involves an image; the replacement asserts getMachineHash() resolves the project snapshot.

Not verified locally: a successful rollup application boot and the nvram integration tests, which need Docker with riscv64. CI covers those.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UTEd5g3mF849BATTstssR3

@changeset-bot

changeset-bot Bot commented Aug 5, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 94b49cd

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@cartesi/cli Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@socket-security

socket-security Bot commented Aug 5, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​cartesi/​machine@​1.0.0-alpha.2721009894100

View full report

@github-actions

github-actions Bot commented Aug 5, 2026 •

Copy link
Copy Markdown
Contributor

Coverage Report

Status Category Percentage Covered / Total
🟢 Lines 94.12% (🎯 0%) 11422 / 12135
🔵 Statements 94.12% 11422 / 12135
🔵 Functions 82.66% 205 / 248
🔵 Branches 0% 0 / 0
📁 File Coverage (20 files)
File Lines Statements Functions Branches Uncovered Lines
apps/cli/src/base.ts 🔴 29.3% 🔴 29.3% 🔴 46.15% 🔴 0% 52, 56-65, 69, 83, 91-164, ...
apps/cli/src/builder/directory.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
apps/cli/src/builder/docker.ts 🟢 86.72% 🟢 86.72% 🟡 66.67% 🔴 0% 75-77, 79, 109-111, 169-178
apps/cli/src/builder/empty.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
apps/cli/src/builder/none.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
apps/cli/src/builder/nvram.ts 🟢 96.88% 🟢 96.88% 🟢 100% 🔴 0% 27
apps/cli/src/builder/tar.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
apps/cli/src/compose/anvil.ts 🟡 79.25% 🟡 79.25% 🟢 100% 🔴 0% 19-29
apps/cli/src/compose/builder.ts 🟢 99.79% 🟢 99.79% 🟢 100% 🔴 0% 228
apps/cli/src/compose/bundler.ts 🔴 4.82% 🔴 4.82% 🔴 0% 🔴 0% 8-40, 44-75, 79-92
apps/cli/src/compose/common.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
apps/cli/src/compose/database.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
apps/cli/src/compose/explorer.ts 🔴 6.67% 🔴 6.67% 🔴 0% 🔴 0% 10-38, 43-55, 59-72
apps/cli/src/compose/node.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
apps/cli/src/compose/passkey.ts 🔴 8.33% 🔴 8.33% 🔴 0% 🔴 0% 9-18, 23-42, 46-59
apps/cli/src/compose/paymaster.ts 🔴 7.69% 🔴 7.69% 🔴 0% 🔴 0% 8-21, 25-44, 48-61
apps/cli/src/compose/proxy.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
apps/cli/src/config.ts 🟢 95.7% 🟢 95.7% 🟢 96.3% 🔴 0% 90-91, 310, 319, 328, 422, ...
apps/cli/src/contracts.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
...rc/errors/ForkChainValidationError.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -

tuler commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

Note on the @deroll dependency

This PR depends on @deroll/cm@alpha and @deroll/genext2fs@alpha, which are personal-scope packages. That is expected to be temporary.

Per cartesi/rollups-ts#133 (comment):

Next step is to bring @deroll/cmio here as @cartesi/rollup and @deroll/cm as @cartesi/machine.
Will wait for machine-emulator 0.21.0 release for that.

So @deroll/cm → @cartesi/machine is already planned, and the release it was waiting on is the one this PR pins (@deroll/cm@0.2.0-alpha.4 links against emulator 0.21.0). Earlier in that thread the per-platform packages are described as @cartesi/machine-{darwin,linux}-{arm64,x64}, "currently published as @deroll/cm" — so the platform optionalDependencies get renamed alongside the main package.

Once those land, the migration here should be a rename rather than a rework: the API surface is the same, and the only imports to touch are

  • src/exec/cartesi-machine.ts
  • src/exec/cartesi-machine-stored-hash.ts
  • src/machine.ts (types only)
  • the external array in apps/cli/build.ts

@deroll/genext2fs is newer than that comment and isn't named in the plan, so it's worth deciding separately whether it also moves to @cartesi/* — it is the one dependency here under GPL-2.0-only (inherited from xgenext2fs), while the CLI is Apache-2.0.


Generated by Claude Code

Comment thread apps/cli/tests/integration/exec/cartesi-machine-stored-hash.test.ts Outdated

tuler commented Aug 12, 2026

Copy link
Copy Markdown
Member Author

Migrated to @cartesi/machine

@cartesi/machine@1.0.0-alpha.0 was published earlier today, so the swap my previous comment described as pending is done in 1abc6bb. @deroll/cm is gone from the dependency tree.

It was a rename, as expected — the two packages have an identical export surface (83 exports, same names; the only .d.ts difference is formatting), both link against emulator 0.21.0, and the platform packages carry the @cartesi/machine-{darwin,linux}-{arm64,x64} names from the issue thread. It also moves this dependency from @deroll's Apache-2.0 to the official package under the same license.

Verified equivalence rather than assuming it: rebuilding the same machine (same drives, kernel, env, workdir, user) before and after produces the same root hash, 0x3231ba4754a106cf5c32bdec49d159823dced0e28c8e27565434c834fde94380. Lint clean, 169 unit tests pass, and build + hash still work end-to-end against the bundled CLI.

@deroll/genext2fs is unchanged — still the open question from the previous comment.


Generated by Claude Code

@tuler
tuler force-pushed the claude/replace-xgenext2fs-cartesi-machine-rhhnsk branch from 1abc6bb to d02ec21 Compare August 17, 2026 20:39
@tuler
tuler force-pushed the claude/replace-xgenext2fs-cartesi-machine-rhhnsk branch from d02ec21 to be559bf Compare August 17, 2026 20:48
@tuler tuler changed the title feat(cli): replace xgenext2fs and cartesi-machine spawns with native bindings feat(cli): replace the cartesi-machine spawn with @cartesi/machine Aug 17, 2026
@tuler
tuler force-pushed the claude/replace-xgenext2fs-cartesi-machine-rhhnsk branch from be559bf to eeee1e5 Compare August 17, 2026 21:11
@brunomenezes brunomenezes moved this to 🧑‍💻 In Progress in Rollups Tooling Aug 19, 2026
Configure, boot, store and hash the Cartesi machine through the @cartesi/machine
N-API bindings, instead of spawning cartesi-machine and
cartesi-machine-stored-hash (falling back to running them inside the SDK docker
image).

machine.ts now translates a cartesi.toml Config into an emulator MachineConfig
directly, mirroring what the cartesi-machine CLI does with its command line:
the boot args it appends to, the init script (splash, flash drive mounts and
chowns, nvram permissions and chowns, environment exports, WORKDIR and USER),
the flash drives with root first so it lands on pmem0, the nvram ranges in
declaration order, and the virtio console setup for an interactive shell.
buildMachineConfig is a pure function, covered by unit tests.

With no SDK image to take the kernel from, images.ts downloads the pinned
cartesi/machine-linux-image release on first use, verifies its checksum and
caches it under XDG_CACHE_HOME. CARTESI_IMAGES_PATH and machine.ram_image still
win.

The run loop can capture the guest console into a file and return it, which is
how a test reads what the machine printed now that there is no child process to
read stdout from.

The emulator moves from 0.20 to 0.21, so machine hashes change. The standalone
binaries are gone: a bun single file executable has no node_modules, and the
addon resolves its platform .node at runtime, so it cannot be embedded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UTEd5g3mF849BATTstssR3
@tuler
tuler force-pushed the claude/replace-xgenext2fs-cartesi-machine-rhhnsk branch from eeee1e5 to 94b49cd Compare October 6, 2026 14:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Todo
Status: 🧑‍💻 In Progress

Development

Successfully merging this pull request may close these issues.

3 participants